﻿Public MustInherit Class MidiEvent

    Public Property TimeOffset As Integer

    Public Shared Function FromStream(Input As BigEndianBinaryReader, ExceptionOffsetPosition As Long, ByRef PreviousStatusByte As Byte) As MidiEvent

        Dim DeltaTime As Integer = Input.ReadVariableLengthInt

        Dim StatusByte As Byte
        If (Input.PeekByte And &HF0) < &H80 Then
            StatusByte = PreviousStatusByte
        Else
            StatusByte = Input.ReadByte
        End If

        'Channel Event
        If (StatusByte And &HF0) <> &HF0 Then

            Dim Channel As Byte = StatusByte And CByte(&HF)
            Select Case StatusByte And &HF0

                Case &H80
                    FromStream = New NoteOffEvent With
                                 {
                                     .Channel = Channel,
                                     .Pitch = Input.ReadByte,
                                     .Velocity = Input.ReadByte
                                 }

                Case &H90
                    FromStream = New NoteOnEvent With
                                 {
                                     .Channel = Channel,
                                     .Pitch = Input.ReadByte,
                                     .Velocity = Input.ReadByte
                                 }

                Case &HA0
                    FromStream = New NoteAfterTouchEvent With
                                 {
                                     .Channel = Channel,
                                     .Pitch = Input.ReadByte,
                                     .Value = Input.ReadByte
                                 }

                Case &HB0
                    FromStream = New ControlChangeEvent With
                                 {
                                     .Channel = Channel,
                                     .Control = Input.ReadByte,
                                     .Value = Input.ReadByte
                                 }

                Case &HC0
                    FromStream = New ProgramChangeEvent With
                                 {
                                     .Channel = Channel,
                                     .Program = Input.ReadByte
                                 }

                Case &HD0
                    FromStream = New ChannelAfterTouchEvent With
                                 {
                                     .Channel = Channel,
                                     .Value = Input.ReadByte
                                 }

                Case &HE0
                    Dim LSB As Byte = Input.ReadByte
                    Dim MSB As Byte = Input.ReadByte
                    FromStream = New PitchBendEvent With
                                 {
                                     .Channel = Channel,
                                     .Value = ((MSB And CShort(&H7F)) << 7) Or (LSB And CShort(&H7F))
                                 }

                Case Else
                    Throw New MidiFileFormatException(Input.Position + ExceptionOffsetPosition - 1, "Unknown Event Type 0x" & StatusByte.ToString("X2"))

            End Select

            'Meta Event
        ElseIf StatusByte = &HFF Then

            Dim MetaEventTypeByte As Byte = Input.ReadByte
            Dim WrongLengthExceptionPosition As Long = Input.Position + ExceptionOffsetPosition
            Dim EventLength As Integer = Input.ReadVariableLengthInt
            Select Case MetaEventTypeByte

                Case &H0
                    If EventLength <> 2 Then _
                        Throw New MidiFileFormatException(WrongLengthExceptionPosition, "Wrong length for sequence number event")

                    Dim LSB As Byte = Input.ReadByte
                    Dim MSB As Byte = Input.ReadByte
                    FromStream = New SequenceNumber With
                                 {
                                     .Value = (CUShort(MSB) << 8) Or LSB
                                 }

                Case &H1
                    FromStream = New TextEvent With
                                 {
                                     .Text = System.Text.Encoding.UTF8.GetString(Input.ReadBytes(EventLength), 0, EventLength)
                                 }

                Case &H2
                    FromStream = New CopyrightNotice With
                                 {
                                     .Text = System.Text.Encoding.UTF8.GetString(Input.ReadBytes(EventLength), 0, EventLength)
                                 }

                Case &H3
                    FromStream = New TrackName With
                                 {
                                     .Text = System.Text.Encoding.UTF8.GetString(Input.ReadBytes(EventLength), 0, EventLength)
                                 }

                Case &H4
                    FromStream = New InstrumentName With
                                 {
                                     .Text = System.Text.Encoding.UTF8.GetString(Input.ReadBytes(EventLength), 0, EventLength)
                                 }

                Case &H5
                    FromStream = New Lyrics With
                                 {
                                     .Text = System.Text.Encoding.UTF8.GetString(Input.ReadBytes(EventLength), 0, EventLength)
                                 }

                Case &H6
                    FromStream = New Marker With
                                 {
                                     .Text = System.Text.Encoding.UTF8.GetString(Input.ReadBytes(EventLength), 0, EventLength)
                                 }

                Case &H7
                    FromStream = New CuePoint With
                                 {
                                     .Text = System.Text.Encoding.UTF8.GetString(Input.ReadBytes(EventLength), 0, EventLength)
                                 }

                Case &H20
                    If EventLength <> 1 Then _
                        Throw New MidiFileFormatException(WrongLengthExceptionPosition, "Wrong length for channel prefix event")

                    FromStream = New ChannelPrefix With
                                 {
                                     .Channel = Input.ReadByte
                                 }

                Case &H2F
                    If EventLength <> 0 Then _
                        Throw New MidiFileFormatException(WrongLengthExceptionPosition, "Wrong length for end of track event")

                    FromStream = New EndOfTrack

                Case &H51
                    If EventLength <> 3 Then _
                        Throw New MidiFileFormatException(WrongLengthExceptionPosition, "Wrong length for tempo event")

                    FromStream = New TempoEvent With
                                 {
                                     .MicrosecondsPerQuarterNote = Input.ReadInt24
                                 }

                Case &H54
                    If EventLength <> 5 Then _
                        Throw New MidiFileFormatException(WrongLengthExceptionPosition, "Wrong length for SMPTE offset event")

                    FromStream = New SMPTEOffset With
                                 {
                                     .Hours = Input.ReadByte,
                                     .Minutes = Input.ReadByte,
                                     .Seconds = Input.ReadByte,
                                     .Frames = Input.ReadByte,
                                     .SubFrames = Input.ReadByte
                                 }

                Case &H58
                    If EventLength <> 4 Then _
                        Throw New MidiFileFormatException(WrongLengthExceptionPosition, "Wrong length for time signature event")

                    FromStream = New TimeSignature With
                                 {
                                     .Numerator = Input.ReadByte,
                                     .Denominator = Input.ReadByte,
                                     .Metronome = Input.ReadByte,
                                     .NumberOf32ndNotes = Input.ReadByte
                                 }

                Case &H59
                    If EventLength <> 2 Then _
                        Throw New MidiFileFormatException(WrongLengthExceptionPosition, "Wrong length for key signature event")

                    Dim Key As Integer = Input.ReadByte
                    If Key >= 128 Then Key = 256 - Key

                    FromStream = New KeySignature With
                                 {
                                     .Key = CSByte(Key),
                                     .Scale = Input.ReadByte
                                 }

                Case &H7F
                    FromStream = New SequencerSpecific With
                                 {
                                     .Data = Input.ReadBytes(EventLength)
                                 }

                Case Else
                    FromStream = New UnknownMetaEvent With
                                 {
                                     .MetaEventType = MetaEventTypeByte,
                                     .Data = Input.ReadBytes(EventLength)
                                 }

            End Select

            'System Exclusive Event
        ElseIf StatusByte = &HF0 Or StatusByte = &HF7 Then

            Dim EventLength As Integer = Input.ReadVariableLengthInt
            FromStream = New SystemExclusiveEvent With
                         {
                             .IsTypeF7 = StatusByte = &HF7,
                             .Data = Input.ReadBytes(EventLength)
                         }

            'Unknown Event
        Else
            Throw New MidiFileFormatException(Input.Position + ExceptionOffsetPosition - 1, "Unknown Event Type 0x" & StatusByte.ToString("X2"))

        End If

        FromStream.TimeOffset = DeltaTime

        PreviousStatusByte = StatusByte

    End Function

End Class

